using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using AenSidhe.Utility.Plugin.Engine;
using MedSoft.Library.Configuration;
using MedSoft.Library.Logging;
using MedSoft.Library.Utilities;
using UrDnd.IrcBot.ComponentModel;
using UrDnd.IrcBot.ComponentModel.Configuration;
using UrDnd.IrcClient.Constants;
using UrDnd.IrcClient.Events;

namespace UrDnd.IrcBot.Service
{
	[Log]
	public class IrcBot
	{
		private readonly List<IAnswerer> _answerers = new List<IAnswerer>();

		private readonly BotConfigurationSection _config =
			ConfigurationManager.GetConfigurationSection<BotConfigurationSection>(BotConfigurationSection.CONFIG_SECTION_NAME);

		private readonly IrcClient.IrcClient _ircClient;
		private uint _connectionAttempts;
		private int _nickNameIndex;
		private int _serverIndex;
	    private IrcBotService _service;

		public IrcBot()
		{
			_ircClient = new IrcClient.IrcClient
			             	{
			             		AutoReconnect = false,
			             		AutoRejoin = true,
			             		AutoRetry = true,
			             		ChannelSyncing = true,
			             		SendDelay = 200
			             	};

			FillAnswerers();

			_ircClient.ChannelMessageReceived += MessageReceived;
			_ircClient.QueryMessageReceived += MessageReceived;
			_ircClient.NickChanged += NickChanged;
			_ircClient.Disconnected += Disconnected;
		}

		public bool DisconnectedByUser { get; set; }

		private void Disconnected(object sender, EventArgs e)
		{
			Singleton<Logger>.Instance.Warn("Bot is disconnected");
            if (!DisconnectedByUser && _config.AutoReconnect)
                LaunchInternal();
            else
                Thread.CurrentThread.Abort(false);
		}

		private void NickChanged(object sender, NickNameChangedEventArgs e)
		{
			if (e.OldNickName != _config.NickNames[_nickNameIndex].NickName) return;
			NextNickNameIndex();
			_ircClient.Nick(_config.NickNames[_nickNameIndex].NickName);
			_ircClient.WriteLine(_config.NickNames[_nickNameIndex].Ident);
		}

	    private void LaunchInternal()
		{
			try
			{
				if (Connect())
				{
					Singleton<Logger>.Instance.Info("Bot is connected");
					_ircClient.Login(_config.NickNames[_nickNameIndex].NickName, _config.NickNames[_nickNameIndex].NickName);
					_ircClient.WriteLine(_config.NickNames[_nickNameIndex].Ident);

					foreach (ChannelConfiguration channelConfiguration in _config.Channels)
						_ircClient.Join(channelConfiguration.Name);

					_ircClient.Listen();
				}
			}
			catch (ThreadAbortException ex)
			{
			    var isStoppedByScm = false;

                if (ex.ExceptionState is bool)
                    isStoppedByScm = (bool) ex.ExceptionState;

                _ircClient.Quit();
				Thread.ResetAbort();

                if (!isStoppedByScm && _service != null)
                    _service.Stop();
			}
		}

        public void Launch(object state)
        {
            if (state is IrcBotService)
                _service = (IrcBotService) state;

            LaunchInternal();
        }

		private bool Connect()
		{
			bool connected;

			do
			{
				_connectionAttempts++;
				_ircClient.Encoding = Encoding.GetEncoding(_config.Servers[_serverIndex].Encoding);
				connected = _ircClient.Connect(_config.Servers[_serverIndex].Address, _config.Servers[_serverIndex].Port);

				if (connected)
					_connectionAttempts = 0;
				else
					NextServer();
			} while (!(connected || HaveWeAnyAttemptsYet()));

			return connected;
		}

		private void NextServer()
		{
			NextIndex(ref _serverIndex, _config.Servers.Count);
		}

		private void NextNickNameIndex()
		{
			NextIndex(ref _nickNameIndex, _config.NickNames.Count);
		}

		private static void NextIndex(ref int index, int count)
		{
			index = index + 1 < count ? index + 1 : 0;
		}

		private bool HaveWeAnyAttemptsYet()
		{
			return _config.ConnectionAttemptNumber == 0 || _connectionAttempts < _config.ConnectionAttemptNumber;
		}

		private void MessageReceived(object sender, RawDataReceivedEventArgs e)
		{
			if (_ircClient.IsMe(e.Data.NickName))
				return;

			foreach (var answerer in _answerers)
			{
				var answer = answerer.Answer(e.Data);
				if (answer == null)
					continue;

				_ircClient.SendMessage(SendType.Message, e.Data.Channel, answer);
			}
		}

		#region Engine load

		private void FillAnswerers()
		{
			var engine = new Engine<IAnswerer>();
			engine.PluginInstantinated += (( sender, e ) => { if (e.Instance != null) _answerers.Add(e.Instance); });
			engine.LoadAndInstantinate();
		}

		#endregion
	}
}